package com.erp.framework.model.vo;

import java.beans.PropertyDescriptor;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.PropertyUtils;

import com.erp.framework.exceptions.CheckFieldLengthException;
import com.erp.framework.model.dao.SimpleBaseDao;
import com.erp.framework.model.pk.SimpleBasePk;
import com.erp.framework.model.schema.MetaData;
import com.erp.framework.model.schema.TableSchema;
import com.erp.framework.types.CustomBigInteger;
import com.erp.framework.util.CommonUtil;
import com.erp.framework.util.ConvertUtil;
import com.erp.framework.util.logger.Logger;

public abstract class SimpleBaseVo extends VersionableVo {

    /**
     * @ddeprecated
     */
    protected ArrayList tableSchema = new ArrayList();

    /**
     * @ddeprecated
     */
    public ArrayList getTableSchema() {
        return this.tableSchema;
    }

    /**
     * @ddeprecated
     */
    public void setTableSchema(ArrayList tableSchema) {
        this.tableSchema = tableSchema;
    }

    protected String tableSchemaName = null;
    protected Class tableSchemaType = null;
    protected String[] childTableSchemaNames;
    protected Class[] childTableSchemaTypes;

    public String getTableSchemaName() {
        return tableSchemaName;
    }

    public String[] getChildTableSchemaNames() {
        return childTableSchemaNames;
    }

    public Class getTableSchemaType() {
        return tableSchemaType;
    }

    public Class[] getChildTableSchemaTypes() {
        return childTableSchemaTypes;
    }

    // to be overwritten by child classes
    // must declared as abstract for next framework release
    protected void setTableSchemaName() {
    }

    // to be overwritten by child classes
    // must declared as abstract for next framework release
    protected void setChildTableSchemaNames() {
    }

    protected boolean deleteForever = false;
    protected boolean isDelete = false;
    protected boolean isExisting = false;
    protected boolean isDirty = true;
    protected ArrayList childsList = new ArrayList();
    protected ArrayList errorList = new ArrayList();
    protected ArrayList errorChilds = new ArrayList();
    protected String childTypesName[];
    protected Class childTypes[];
    protected String daoName;
    protected Class daoType;
    protected SimpleBaseVo parent;
    protected SimpleBaseVo oldModel;
    protected String bizLogicName = null;
    protected Class bizLogicType = null;

    public void setIsDelete(boolean isDelete) {
        this.isDelete = isDelete;
    }

    public void setIsExisting(boolean isExisting) {
        this.isExisting = isExisting;
    }

    public void setIsDirty(boolean isDirty) {
        this.isDirty = isDirty;
    }

    public void setParent(SimpleBaseVo parent) {
        this.parent = parent;
    }

    protected abstract void setDaoName();

    protected abstract void setChildTypesName();

    // to be overwritten by child classes,
    // must declared as abstract for next framework release
    protected void setBizLogicName() {
    };

    public boolean getIsDelete() {
        return this.isDelete;
    }

    public boolean getIsExisting() {
        return this.isExisting;
    }

    public boolean getIsDirty() {
        return this.isDirty;
    }

    public ArrayList getChildsList() {
        return childsList;
    }

    public SimpleBaseVo getParent() {
        return parent;
    }

    public Class getDaoType() {
        return daoType;
    }

    public Class[] getChildTypes() {
        return childTypes;
    }

    public String getDaoName() {
        return daoName;
    }

    public String[] getChildTypesName() {
        return this.childTypesName;
    }

    public String getBizLogicName() {
        return this.bizLogicName;
    }

    public Class getBizLogicType() {
        return this.bizLogicType;
    }

    public SimpleBaseVo getChildVO(SimpleBasePk childPK, int childType) {
        return getChildVO(childPK, childTypes[childType]);
    }

    public SimpleBaseVo getChildVO(Object pk, Class childType) {
        SimpleBasePk childPK = (SimpleBasePk) pk;
        SimpleBaseVo foundChildVO = null;
        try {
            ChildList childList = this.getChilds(childType);
            for (int i = 0; i < childList.size(); i++) {
                SimpleBaseVo childVO = (SimpleBaseVo) childList.get(i);
                
                //childPK is null if it is read-only and possible got data from a DB VIEW 
                if ( (childPK != null) && (childPK.equals((childVO.getPrimaryKey()))) ) {
                    foundChildVO = childVO;
                    break;
                }
            }
        } catch (Exception e) {
            Logger.logExceptions(this, e);
        }
        return foundChildVO;
    }

    public abstract SimpleBasePk getPrimaryKey();

    public SimpleBaseVo(){
        try {
            setChildTypesName();
            setDaoName();
            setBizLogicName();
            setSchema(); // @deprecated
            setTableSchemaName(); // use this instead
            setChildTableSchemaNames(); // also stores child schema

            // add try catch for each block....

            try {
                if (tableSchemaType == null && tableSchemaName != null) {
                    tableSchemaType = this.getClass().getClassLoader().loadClass(tableSchemaName);
                }
            } catch (Exception e) {
                throw new Exception("cannot load table schema class - [" + tableSchemaName + "] not found or invalid");
            }

            if (childTypes == null && getChildTypesName() != null) {
                childTypes = new Class[getChildTypesName().length];
                for (int i = 0; i < getChildTypesName().length; i++) {
                    String typeName = getChildTypesName()[i];
                    try {
                        childTypes[i] = this.getClass().getClassLoader().loadClass(typeName);
                    } catch (Exception e) {
                        throw new Exception("cannot load child class - [" + typeName + "] not found or invalid");
                    }
                }
            }

            if (childTableSchemaTypes == null && getChildTableSchemaNames() != null) {
                childTableSchemaTypes = new Class[getChildTableSchemaNames().length];
                for (int i = 0; i < getChildTableSchemaNames().length; i++) {
                    String typeName = getChildTableSchemaNames()[i];
                    try {
                        childTableSchemaTypes[i] = this.getClass().getClassLoader().loadClass(typeName);
                    } catch (Exception e) {
                        throw new Exception("cannot load child table schema class - [" + typeName + "] not found or invalid");
                    }
                }
            }

            if (daoType == null && daoName != null) {
                try{
                    daoType = this.getClass().getClassLoader().loadClass(daoName);
                } catch (Exception e) {
                    throw new Exception("cannot load dao class - [" + daoName + "] not found or invalid");
                }
            }

            if (bizLogicType == null && bizLogicName != null) {
                try{
                    bizLogicType = this.getClass().getClassLoader().loadClass(bizLogicName);
                } catch (Exception e) {
                    throw new Exception("cannot load biz logic class - [" + bizLogicName + "] not found or invalid");
                }   
            }
            
        } catch (Exception e) {
            Logger.logExceptions(this, e);
        }
    }

    /**
     * if childType not exists, it will add a new ChildList
     */
    public void storeChilds(ChildList childLst) {
        try {
            boolean isChildTypeExist = false;
            for (int i = 0; i < this.getChildsList().size(); i++) {
                ChildList tmpChildLst = (ChildList) this.getChildsList().get(i);
                if (tmpChildLst.getChildType() == childLst.getChildType()) {
                    isChildTypeExist = true;
                    this.getChildsList().set(i, childLst);
                }
            }// end for i

            if (!isChildTypeExist) {
                this.getChildsList().add(childLst);
            }// end if
        } catch (Exception e) {
            Logger.logExceptions(this, e);
        }
    }// end storeChilds(ChildList childLst)

    public void removeChilds(Class clazz) {
        try {
            for (int i = 0; i < this.getChildsList().size(); i++) {
                ChildList tmpChildLst = (ChildList) this.getChildsList().get(i);
                if (tmpChildLst.getChildType() == clazz) {
                    this.getChildsList().remove(i);
                    break;
                }
            }// end for i
        } catch (Exception e) {
            Logger.logExceptions(this, e);
        }
    }

    public ChildList getChilds(Class childType) {
        ChildList rtn = null;

        try {
            for (int i = 0; (this.getChildsList() != null) && (i < this.getChildsList().size()); i++) {
                ChildList tmpChildLst = (ChildList) this.getChildsList().get(i);
                if (tmpChildLst.getChildType() == childType) {
                    rtn = tmpChildLst;
                    break;
                }
            }// end for i

            // if not exist, add 1
            if (rtn == null) {

                // do lazy loading
                SimpleBaseVo child = (SimpleBaseVo) childType.newInstance();
                ChildList childs = null;

                if (child.getDaoType() != null) {
                	long childstart = System.currentTimeMillis();
                    SimpleBaseDao dao = (SimpleBaseDao) child.getDaoType().newInstance();
                    childs = dao.selectByParent(this);
                    long childend = System.currentTimeMillis();
                    Logger.debug(this, "getChilds(), Load child, "
                    		+ child.getDaoType().getName()		
                    		+ ", in " + ((childend-childstart)/100.000) + " seconds");
                }
                
                if (childs == null) {
                    childs = new ChildList(childType);
                }
                this.storeChilds(childs);
                // end lazy loading

                // / recursive call
                // rtn=getChilds(childType);
                for (int i = 0; i < this.getChildsList().size(); i++) {
                    ChildList tmpChildLst = (ChildList) this.getChildsList().get(i);
                    if (tmpChildLst.getChildType() == childType) {
                        rtn = tmpChildLst;
                    }
                }// end for i
            } // end if (rtn==null)
        } catch (Exception e) {
            Logger.logExceptions(this, e);
        }
        return rtn;
    }

    public ChildList getLocalChilds(Class clazz) {
        ChildList rslt = null;

        for (int i = 0; i < this.getChildsList().size(); i++) {
            ChildList tmpChildLst = (ChildList) this.getChildsList().get(i);
            if (tmpChildLst.getChildType() == clazz) {
                rslt = tmpChildLst;
                break;
            }
        }

        return rslt;
    }

    public boolean objCompare(Object obj1, Object obj2) {
        boolean isEqual = false;

        if (obj1 != null) {
            if (obj1.equals(obj2))
                isEqual = true;
        } else {
            if (obj2 == null)
                isEqual = true;
        }

        return isEqual;
    }

    public boolean isErrorEmpty() {
        boolean rtn = true;
        try {
            // rtn=this.errorList.isEmpty();
            if (this.errorList.isEmpty() && this.errorChilds.isEmpty()) {
                rtn = true;
            }// end if
            else {
                rtn = false;
            }
        } catch (Exception e) {
            Logger.logExceptions(this, e);
        }
        return rtn;
    }// end isErrorEmpty

    public void addErrorKey(String errorKey) {
        try {
            this.errorList.add(errorKey);
        } catch (Exception e) {
            Logger.logExceptions(this, e);
        }
    }// end addErrorKey

    public void addErrorKeys(Collection errorKeys) {
        try {
            this.errorList.addAll(errorKeys);
        } catch (Exception e) {
            Logger.logExceptions(this, e);
        }
    }// end addErrorKeys

    public ArrayList getErrorKeys() {
        ArrayList errs = null;
        try {
            errs = this.errorList;
        } catch (Exception e) {
            Logger.logExceptions(this, e);
        }
        return errs;
    }

    public void addErrorChild(SimpleBaseVo child) {
        try {
            this.errorChilds.add(child);
        } catch (Exception e) {
            Logger.logExceptions(this, e);
        }
    }// end addErrorChild

    public ArrayList getErrorChilds() {
        ArrayList errs = null;
        try {
            errs = this.errorChilds;
        } catch (Exception e) {
            Logger.logExceptions(this, e);
        }
        return errs;
    }// end getErrorChilds

    public boolean hasError(String errKey) {
        boolean rtn = false;
        try {
            if (this.errorList.contains(errKey)) {
                rtn = true;
            }
        } catch (Exception e) {
            Logger.logExceptions(this, e);
        }
        return rtn;
    }// end

    public void unsetErrorKeys() {
        this.errorList = new ArrayList();
    }// end unsetErrorKeys

    public void unsetErrorChilds() {
        this.errorChilds = new ArrayList();
    }// /// end unsetErrorKeys

    public SimpleBaseVo getNode(Class clazz) {
        if (this.getClass() == clazz) {
            return this;
        } else if (this.getParent() != null) {
            return this.getParent().getNode(clazz);
        } else {
            return null;
        }
    }// end getParent

    public SimpleBaseVo getNode(String classType) {
        SimpleBaseVo rtn = null;
        try {
            Class clazz = this.getClass().getClassLoader().loadClass(classType);
            rtn = getNode(clazz);
        } catch (Exception e) {
            Logger.logExceptions(this, e);
        }
        return rtn;
    }

    public boolean checkFieldLength() throws Exception, CheckFieldLengthException {
        boolean rslt = true;

        ArrayList schema = null;
        if (this.getTableSchemaType() != null) { // the new way
            schema = ((TableSchema) this.getTableSchemaType().newInstance()).getTs();
        } else { // the old way
            if (this.tableSchema == null) {
                return false;
            } else {
                schema = this.tableSchema;
            }
        }

        try {
            for (int i = 0; i < schema.size(); i++) {
                MetaData metaData = (MetaData) schema.get(i);
                if (!"java.lang.String".equals(metaData.getColumnClassName())) {
                    continue;
                }

//                if(metaData.getVoPropertyName().equals("termsCode")){
//                	System.out.println("***");
//                } 
                try {
                String data = CommonUtil.nullToString((String) PropertyUtils
                        .getSimpleProperty(this, metaData.getVoPropertyName()));
                byte[] dataByte = data.getBytes("UTF-8");
                
                if (dataByte.length > metaData.getColumnSize()) {
                    rslt = false;
                    throw new CheckFieldLengthException(metaData.getColumnName() + " exceeds the field length = ["
                            + metaData.getColumnSize() + "]");
                }
                }catch (CheckFieldLengthException cfe) {
                	throw cfe;
                }catch (Exception e) {
                	Logger.all(this, e.getMessage());
                }
            }

            if (rslt) {
                for (int i = 0; childTypes != null && i < childTypes.length; i++) {
                    ChildList childList = this.getChilds(childTypes[i]);
                    for (int j = 0; j < childList.size(); j++) {
                        SimpleBaseVo childVO = (SimpleBaseVo) childList.get(j);
                        rslt = childVO.checkFieldLength();
                    }
                }
            }

        } catch (CheckFieldLengthException cfe) {
            Logger.logExceptions(this, cfe);
            throw (cfe);
        } catch (Exception e) {
            Logger.logExceptions(this, e);
            rslt = false;
            throw (e);
        }

        return rslt;
    }

    protected void setSchema() {

    }

    public SimpleBaseVo getOldModel() {

        return oldModel;
    }

    public void setOldModel(SimpleBaseVo oldModel) {

        this.oldModel = oldModel;
    }

    public static boolean isIdentical(SimpleBaseVo src, SimpleBaseVo dest)
    // throws IllegalAccessException, InvocationTargetException, NoSuchMethodException
    {
        boolean identical = true;
        try {
        	if((src == null) && (dest == null)){
        		return true;
        	}
        	if(src == null){
        		return false;
        	}
        	if(dest == null){
        		return false;
        	}
        	
            PropertyDescriptor srcDescriptors[] = PropertyUtils.getPropertyDescriptors(src);
            for (int i = 0; i < srcDescriptors.length; i++) {
                String name = srcDescriptors[i].getName();

                // Poor style! You are hardcoding an exception here
                if (name.equals("parent") || name.equals("childTypesName") || name.equals("childTypes")
                        || name.equals("childsList")) {
                    continue;
                }

                if (PropertyUtils.getPropertyDescriptor(dest, name) != null) {
                    Object srcValue = PropertyUtils.getSimpleProperty(src, name);
                    Object destValue = PropertyUtils.getSimpleProperty(dest, name);
                    if (srcValue == null && destValue == null)
                        continue;

                    if ((srcValue == null || destValue == null) || !srcValue.toString().equals(destValue.toString())) {

                        Logger.debug("isIdentical(), Not matching for " + name + ", src = " + srcValue + ", dest = " + destValue);
                        identical = false;
                        break;
                    }
                }
            }
        } catch (Exception e) {
            Logger.logExceptions(e);
            return false;
        }
        return identical;
    }

    public void assignKey(SimpleBasePk pk) {

        try {
            Map pkMap = BeanUtils.describe(pk);
            ConvertUtil.deregister();
            pkMap.remove("class");
            BeanUtils.populate(this, pkMap);

            if (childTypes != null) {
                for (int i = 0; i < childTypes.length; i++) {
                    ChildList childList = getChilds(childTypes[i]);
                    if (childList != null) {
                        for (Iterator it = childList.iterator(); it.hasNext();) {
                            SimpleBaseVo childVO = (SimpleBaseVo) it.next();
                            BeanUtils.populate(childVO, pkMap);
                            childVO.assignKey(pk);
                        }
                    }
                }
            }
        } catch (Exception e) {
            Logger.logExceptions(this, e);
        }
    }

    public boolean isDeleteForever() {
        return deleteForever;
    }

    public void setDeleteForever(boolean deleteForever) {
        this.deleteForever = deleteForever;
    }
}
